package com.xiaoyu.lanling.feature.videocall.controller

import `in`.srain.cube.util.CheckUtil
import `in`.srain.cube.util.LocalDisplay
import `in`.srain.cube.util.NetworkStatusManager
import `in`.srain.cube.util.UTUtil
import `in`.srain.cube.util.internal.AppCallback
import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.app.Service
import android.content.Context
import android.media.AudioManager
import android.media.AudioManager.OnAudioFocusChangeListener
import android.media.MediaPlayer
import android.os.Bundle
import android.os.SystemClock
import android.os.Vibrator
import android.text.TextUtils
import android.view.View
import android.view.animation.AccelerateDecelerateInterpolator
import android.view.animation.DecelerateInterpolator
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.FragmentActivity
import com.facebook.drawee.view.SimpleDraweeView
import com.facebook.imagepipeline.postprocessors.IterativeBoxBlurPostProcessor
import com.github.florent37.viewanimator.ViewAnimator
import com.opensource.svgaplayer.SVGACallback
import com.opensource.svgaplayer.SVGADrawable
import com.tbruyelle.rxpermissions2.RxPermissions
import com.xiaoyu.base.app.App
import com.xiaoyu.base.app.AppContext
import com.xiaoyu.base.app.GlobalUI
import com.xiaoyu.base.data.UserData
import com.xiaoyu.base.data.UserInfoDataProvider
import com.xiaoyu.base.event.AppEventBus
import com.xiaoyu.base.event.SimpleEventHandler
import com.xiaoyu.base.model.User
import com.xiaoyu.base.utils.RxUtils
import com.xiaoyu.base.utils.extensions.setOnClickDebounceListener
import com.xiaoyu.base.utils.extensions.setViewTag
import com.xiaoyu.base.utils.time.CountDown
import com.xiaoyu.lanling.R
import com.xiaoyu.lanling.common.Constant
import com.xiaoyu.lanling.data.NotificationData
import com.xiaoyu.lanling.data.SvgaData
import com.xiaoyu.lanling.event.gift.GiveGiftAndSendMessageEvent
import com.xiaoyu.lanling.event.gift.ReceiveGiftEvent
import com.xiaoyu.lanling.feature.gift.ChatGiftBottomSheetDialog
import com.xiaoyu.lanling.feature.gift.data.GiftData
import com.xiaoyu.lanling.feature.gift.model.Gift
import com.xiaoyu.lanling.feature.util.mixdata.MixDataUploader
import com.xiaoyu.lanling.feature.videocall.data.VideoCallData
import com.xiaoyu.lanling.feature.videocall.data.VideoCallData.ActionType.ACCEPT
import com.xiaoyu.lanling.feature.videocall.data.VideoCallData.ActionType.CANCEL
import com.xiaoyu.lanling.feature.videocall.data.VideoCallData.ActionType.HANGUP
import com.xiaoyu.lanling.feature.videocall.data.VideoCallData.ActionType.REJECT
import com.xiaoyu.lanling.feature.videocall.log.VideoCallLogger
import com.xiaoyu.lanling.feature.videocall.model.ActionMessageType
import com.xiaoyu.lanling.feature.videocall.model.MessageType.ACTION
import com.xiaoyu.lanling.feature.videocall.model.MessageType.SYSTEM
import com.xiaoyu.lanling.feature.videocall.model.SystemMessageType
import com.xiaoyu.lanling.feature.videocall.util.VideoCallNotificationUtils
import com.xiaoyu.lanling.feature.videomatch.log.VideoMatchLogger
import com.xiaoyu.lanling.feature.voicecall.model.HangupReason
import com.xiaoyu.lanling.media.image.ImageLoadParam
import com.xiaoyu.lanling.media.image.ImageViewUtil
import com.xiaoyu.lanling.media.image.UserImageLoadParam
import com.xiaoyu.lanling.router.Router
import com.xiaoyu.lanling.util.TextViewUtil
import com.xiaoyu.lib_av.datamodel.*
import com.xiaoyu.lib_av.datamodel.CallHangUpType.HangUpTypeDef
import com.xiaoyu.lib_av.datamodel.CallResponseType.REFUSE
import com.xiaoyu.lib_av.listener.AbstractVideoCallLifecycleListener
import com.xiaoyu.lib_av.manager.CallManager
import com.xiaoyu.net.util.NetUtils
import io.reactivex.Completable
import io.reactivex.Flowable
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.subjects.PublishSubject
import kotlinx.android.synthetic.main.video_call_activity.*
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import java.util.*
import java.util.concurrent.TimeUnit

class VideoCallViewController(private val mFragmentActivity: FragmentActivity) : AbstractVideoCallLifecycleListener() {

    private val onAudioFocusChangeListener = OnAudioFocusChangeListener { }

    private var mMainTaskTopActivity: Activity? = null
    private var mVoiceNetworkStatus: TextView? = null

    private var mRingtoneMediaPlayer: MediaPlayer? = null
    private var mVibrator: Vibrator? = null

    private val mIsCaller: Boolean
    private val mCallParams: CallParams?
    private val mFuid: String?
    private val mFromType: String?
    private var mCallDurationInS: Long = 0
    private var mWifiOffPublishSubject: PublishSubject<Boolean>? = null
    private var mVibratorDisposable: Disposable? = null
    private val mNotificationDisposable: Disposable? = null
    private var mDisplayUserDisposable: Disposable? = null
    private var mWifiDisposable: Disposable? = null
    private var mCountDownDisposable: Disposable? = null
    private var mIsHeadset = false
    private var mInitCallTimestamp: Long = 0L
    private var isGiftAnimationPlaying: Boolean = false
    private val mAnimationQueue: Queue<Gift> = LinkedList<Gift>()
    private var mSvgaAnimationDisposable: Disposable? = null
    private var mDisableVideoStartTime = 0L

    private var mGiftAnimator: ViewAnimator? = null

    init {
        mIsCaller = mFragmentActivity.intent.getBooleanExtra(Constant.IntentKey.KEY_CHAT_CALL_IS_CALLER, false)
        mCallParams = mFragmentActivity.intent.getSerializableExtra(Constant.IntentKey.KEY_CHAT_CALL_CALL_PARAM) as? CallParams
        mFuid = mCallParams?.getFuid(UserData.getInstance().isSelf(mCallParams.fromUser.uid))
        mFromType = mFragmentActivity.intent.getStringExtra(Constant.PARAM_FROM)

        if (mFuid == null || mFromType == null) {
            mFragmentActivity.finishAndRemoveTask()
        } else {
            CallManager.instance.create(this)
            initView()
            initBind()
            initEvent()
            initBaseSupport()
            initPermissions()
            initHeadset()
        }
    }

    fun onResume() {
        mMainTaskTopActivity = App.getInstance().topActivity
        dismissNotification()
    }

    fun onStop() {
        if (!TextUtils.isEmpty(mFuid) && isCalling) {
            showNotification()
        }
        clearGiftAnimation()
    }

    fun onDestroy() {
        RxUtils.disposable(mNotificationDisposable, mDisplayUserDisposable, mWifiDisposable, mCountDownDisposable)
        closeRingtoneVibrator()
        stopTime()
        mWifiOffPublishSubject?.onComplete()
        dismissNotification()
        CallManager.instance.destroy()
    }

    private fun initView() {
        mVoiceNetworkStatus = mFragmentActivity.findViewById(R.id.voice_network_status)

        mFragmentActivity.remote_video_container.layoutParams.width = LocalDisplay.SCREEN_WIDTH_PIXELS
        mFragmentActivity.remote_video_container.layoutParams.height = LocalDisplay.SCREEN_HEIGHT_PIXELS
    }

    private fun initBind() {
        mFragmentActivity.voice_calling_gift?.setOnClickDebounceListener {
            val user = UserInfoDataProvider.getInstance().fetchFromMemory(mFuid)
            if (user.isNobody) {
                return@setOnClickDebounceListener
            }
            ChatGiftBottomSheetDialog.show(mFragmentActivity.supportFragmentManager, user, Constant.From.VIDEO_CALL)
        }
        mFragmentActivity.voice_calling_switch_camera?.setOnClickDebounceListener {
            CallManager.instance.switchCamera()
        }
        mFragmentActivity.voice_calling_hangup?.setOnClickDebounceListener {
            if (TextUtils.isEmpty(mFuid)) {
                return@setOnClickDebounceListener
            }
            showHangupConfirmDialog()
        }
        mFragmentActivity.voice_call_hangup?.setOnClickDebounceListener {
            if (TextUtils.isEmpty(mFuid)) {
                return@setOnClickDebounceListener
            }
            showHangupConfirmDialog()
        }
        mFragmentActivity.voice_receive_hangup?.setOnClickDebounceListener {
            sendCallMessage()
            rejectAndFinish()
        }
        mFragmentActivity.voice_receive_answer?.setOnClickDebounceListener {
            receive()
            logEvent(ACCEPT, 0)
        }
        mFragmentActivity.charge_button.setOnClickDebounceListener {
            Router.instance.gotoCoinProductListAndBalanceActivity(mFragmentActivity)
            mFragmentActivity.charge_layout.visibility = View.GONE
            mCountDownDisposable?.dispose()
        }
        mFragmentActivity.voice_call_camera_switch.setOnClickDebounceListener {
            val selected = !it.isSelected
            updateCameraSwitch(selected)
            CallManager.instance.enableLocalVideo(selected)
        }
        mFragmentActivity.voice_receive_camera_switch.setOnClickDebounceListener {
            val selected = !it.isSelected
            updateCameraSwitch(selected)
            CallManager.instance.enableLocalVideo(selected)
        }
        mFragmentActivity.local_video_container.setOnClickDebounceListener {
            val selected = !mFragmentActivity.voice_call_camera_switch.isSelected
            updateCameraSwitch(selected)
            CallManager.instance.enableLocalVideo(selected)
        }
        mFragmentActivity.svga_animation_view.callback = object : SVGACallback {
            override fun onPause() {
                clearGiftAnimation()
            }

            override fun onFinished() {
                mFragmentActivity.svga_animation_view.visibility = View.GONE
                isGiftAnimationPlaying = false
                if (!mAnimationQueue.isEmpty()) {
                    playGiftAnimation(mAnimationQueue.poll())
                }
            }

            override fun onRepeat() {}
            override fun onStep(frame: Int, percentage: Double) {}
        }
    }

    private fun initEvent() {
        AppEventBus.bindContainerAndHandler(mFragmentActivity, object : SimpleEventHandler() {
            @Subscribe(threadMode = ThreadMode.MAIN)
            fun onEvent(event: GiveGiftAndSendMessageEvent) {
                if (event.from != Constant.From.VIDEO_CALL) {
                    return
                }
                enqueueOrPlayGiftAnimation(event.gift)
            }

            @Subscribe(threadMode = ThreadMode.MAIN)
            fun onEvent(event: ReceiveGiftEvent) {
                if (event.from != Constant.From.VIDEO_CALL || event.senderUid != mFuid) {
                    return
                }
                enqueueOrPlayGiftAnimation(event.gift)
            }
        })
    }

    @SuppressLint("CheckResult")
    private fun initBaseSupport() {
        mWifiOffPublishSubject = PublishSubject.create()
        mWifiOffPublishSubject?.let {
            mWifiDisposable = it
                    .throttleFirst(2000, TimeUnit.MILLISECONDS)
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe({ GlobalUI.getInstance().showToast("当前为非 wifi 网络, 不影响通话功能使用") }) { initBaseSupport() }
        }
        if (!NetworkStatusManager.getInstance().isWifi) {
            mWifiOffPublishSubject?.onNext(true)
        }
    }

    @SuppressLint("CheckResult")
    private fun initPermissions() {
        if (mFragmentActivity.isFinishing || mFragmentActivity.isDestroyed) {
            return
        }
        RxPermissions(mFragmentActivity)
                .request(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                .subscribe({ granted: Boolean ->
                    if (granted) {
                        checkNotificationPermission()
                        processData()
                    } else {
                        GlobalUI.getInstance().showToast("请检查权限是否开启")
                        finish()
                    }
                }) { obj: Throwable -> obj.printStackTrace() }
    }

    private fun initHeadset() {
        val audioManager = AppContext.getContext().getSystemService(Context.AUDIO_SERVICE) as AudioManager
                ?: return
        audioManager.requestAudioFocus(onAudioFocusChangeListener, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
        if (audioManager.isWiredHeadsetOn || audioManager.isBluetoothA2dpOn) {
            headsetOn()
        } else {
            headsetOff()
        }
    }

    private fun processData() {
        if (!NetUtils.isConnected(AppContext.getContext(), true)) {
            finish()
            return
        }
        if (TextUtils.isEmpty(mFuid)) {
            finish()
            return
        }
        showUserData(mFuid)
        setupLocalVideo()
        processStatus()
    }

    @SuppressLint("CheckResult")
    private fun showUserData(uid: String?) {
        mDisplayUserDisposable = Completable.fromAction { UserInfoDataProvider.getInstance().ensureUser(uid) }
                .compose(RxUtils.ioToMainCompletableScheduler<Any>())
                .subscribe {
                    val user: User = UserInfoDataProvider.getInstance().fetchFromMemory(uid)
                    ImageViewUtil.loadAvatarRoundWithBoard(mFragmentActivity.user_avatar, user, 96, 2, R.color.user_info_avatar_border, true)
                    mFragmentActivity.user_nickname?.setUser(user)
                }
    }

    private fun setupLocalVideo() {
        CallManager.instance.setupLocalVideo(mFragmentActivity.local_video_container)
        updateCameraSwitch(true)
    }

    private val isCalling: Boolean
        get() = CallManager.instance.isCalling

    private fun processStatus() {
        if (mIsCaller) {
            call()
            showCallStatus()
        } else {
            playRingtoneVibrator()
            showReceiveStatus()
        }
    }

    private fun showCallStatus() {
        mFragmentActivity.voice_status?.text = mFragmentActivity.getString(R.string.video_call_inviting)
        mFragmentActivity.voice_call_layout?.visibility = View.VISIBLE
    }

    private fun showCallingStatus() {
        mFragmentActivity.user_info_group?.visibility = View.GONE
        mFragmentActivity.voice_call_layout?.visibility = View.GONE
        mFragmentActivity.voice_receive_layout?.visibility = View.GONE
        mFragmentActivity.voice_calling_layout?.visibility = View.VISIBLE
    }

    private fun showReceiveStatus() {
        mFragmentActivity.voice_status?.text = mFragmentActivity.getString(R.string.video_call_invited)
        mFragmentActivity.voice_receive_layout?.visibility = View.VISIBLE
    }

    private fun startTime() {
        mFragmentActivity.voice_calling_time?.let {
            it.base = SystemClock.elapsedRealtime()
            it.start()
        }
    }

    private fun stopTime() {
        mFragmentActivity.voice_calling_time?.stop()
    }

    private fun finish() {
        stopTime()
        mFragmentActivity.finishAndRemoveTask()
    }

    private fun sendCallMessage() {
        val hasConnect = mCallDurationInS > 0
        mFuid?.let {
            if (hasConnect) {
                val duration = mCallDurationInS.toInt()
                VideoCallData.sendVoiceStatusMessageForCall(it, duration.toLong(), mFromType ?: "")
                logEvent(HANGUP, duration)
            } else {
                if (mIsCaller) {
                    VideoCallData.sendVoiceStatusMessageForCancel(it)
                    logEvent(CANCEL, 0)
                } else {
                    VideoCallData.sendVoiceStatusMessageForReject(it)
                    logEvent(REJECT, 0)
                }
            }
        }
    }

    private fun rejectAndFinish() {
        reject()
        finish()
    }

    private fun call() {
        mCallParams?.let {
            mInitCallTimestamp = System.currentTimeMillis()
            CallManager.instance.call(it, object : AppCallback<Boolean> {
                override fun onSuccess(t: Boolean) {
                }

                override fun onError(e: Throwable) {
                    finish()
                }
            })
            playRingtone()
        }
    }

    private fun reject() {
        CallManager.instance.response(mFuid, REFUSE)
    }

    private fun hangup(reason: String) {
        VideoCallData.hangup(mFuid, reason)
        CallManager.instance.hangUp(mFuid, CallHangUpType.POSITIVE)
    }

    private fun receive() {
        CallManager.instance.response(mFuid, ACCEPT)
    }

    private fun playRingtone() {
        if (mRingtoneMediaPlayer != null && mRingtoneMediaPlayer?.isPlaying == true) {
            return
        }
        try {
            mRingtoneMediaPlayer = MediaPlayer.create(mFragmentActivity, R.raw.raw_chat_voice_ringtone)
            mRingtoneMediaPlayer?.isLooping = true
            mRingtoneMediaPlayer?.start()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    private fun closeRingtone() {
        mRingtoneMediaPlayer?.pause()
        mRingtoneMediaPlayer?.stop()
        mRingtoneMediaPlayer?.reset()
        mRingtoneMediaPlayer?.release()
        mRingtoneMediaPlayer?.setOnPreparedListener(null)
        mRingtoneMediaPlayer = null
    }

    private fun playVibratorLooping() {
        mVibratorDisposable = Flowable.interval(0, 6, TimeUnit.SECONDS)
                .onBackpressureDrop()
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe({ aLong: Long? -> playVibrator() }) { obj: Throwable -> obj.printStackTrace() }
    }

    private fun playVibrator() {
        if (mVibrator == null) {
            mVibrator = mFragmentActivity.getSystemService(Service.VIBRATOR_SERVICE) as? Vibrator
        }
        mVibrator?.vibrate(2000)
    }

    private fun closeVibrator() {
        RxUtils.disposable(mVibratorDisposable)
        mVibrator?.cancel()
        mVibrator = null
    }

    private fun playRingtoneVibrator() {
        playRingtone()
        playVibratorLooping()
    }

    private fun closeRingtoneVibrator() {
        closeRingtone()
        closeVibrator()
    }

    private fun headsetOn() {
        if (mIsHeadset) {
            return
        }
        mIsHeadset = true
        CallManager.instance.isEnableSpeaker = false
    }

    private fun headsetOff() {
        if (!mIsHeadset) {
            return
        }
        mIsHeadset = false
        CallManager.instance.isEnableSpeaker = true
    }

    private fun showPoorNetworkStatus() {
        mVoiceNetworkStatus?.visibility = View.VISIBLE
    }

    private fun dismissPoorNetworkStatus() {
        mVoiceNetworkStatus?.visibility = View.GONE
    }

    private fun logEvent(event: String, duration: Int) {
        val params = Bundle()
        params.putString("target_uid", mFuid)
        if (TextUtils.equals(CANCEL, event)) {
            UTUtil.getInstance().logEvent("voice_chat_cancel", params)
        } else if (TextUtils.equals(REJECT, event)) {
            UTUtil.getInstance().logEvent("voice_chat_refuse", params)
        } else if (TextUtils.equals(ACCEPT, event)) {
            UTUtil.getInstance().logEvent("voice_chat_accept", params)
        } else if (TextUtils.equals(HANGUP, event)) {
            params.putInt("call_duration", duration)
            UTUtil.getInstance().logEvent("voice_chat_hangup", params)
        }
    }

    private fun showNotification() {
        mCallParams?.let {
            VideoCallNotificationUtils.instance.showNotification(it, mIsCaller, false)
        }
    }

    private fun dismissNotification() {
        VideoCallNotificationUtils.instance.dismissNotification()
    }

    override fun onCallFailed(fuid: String, errorType: String) {
        when (errorType) {
            CallFailType.NO_RESPONSE -> GlobalUI.getInstance().showToast("对方暂不方便接听")
            CallFailType.ILLEGAL_ARGUMENT -> GlobalUI.getInstance().showToast("参数错误")
            CallFailType.NOT_LOGIN -> GlobalUI.getInstance().showToast("暂未连接上服务器，请稍后再试")
            CallFailType.UNKNOWN -> GlobalUI.getInstance().showToast("未知错误")
        }
        // TODO @gaizi
        logEventForCallFailed("unknown")
        hangup(errorType)
        finish()
    }

    override fun onRespondCall(fuid: String, responseType: String) {}
    override fun onCallResponded(fuid: String, responseType: String) {
        when (responseType) {
            CallResponseType.BUSY_LINE, CallResponseType.REFUSE -> {
                GlobalUI.getInstance().showToast("对方暂不方便接听")
                // TODO @gaizi
                logEventForCallFailed(responseType)
                hangup(responseType)
                finish()
            }
            CallResponseType.ACCEPT -> {
            }
        }
    }

    override fun onCancelCall(fuid: String) {
        finish()
    }

    override fun onCallCanceled(callParams: CallParams) {
        // TODO @gaiz
        logEventForCallFailed("cancel")
        finish()
    }

    override fun onCallConnecting(fuid: String) {}
    override fun onCallConnected(fuid: String) {
        startTime()
        showCallingStatus()
        closeRingtoneVibrator()
        connectedWithServer(fuid)
    }

    private fun connectedWithServer(fuid: String) {
        if (mIsCaller) {
            return
        }
        VideoCallData.establishConnection(fuid)
    }

    override fun onCallDisconnected(fuid: String, @HangUpTypeDef hangUpType: String) {
        if (hangUpType == CallHangUpType.NEGATIVE) {
            VideoMatchLogger.logVideoMatchCallRequestHangUp(mCallDurationInS, HangupReason.OTHER_DISCONNECT, CallHangUpType.NEGATIVE)
            VideoCallData.hangup(mFuid, HangupReason.UNKNOWN)
        }
        finish()
    }

    override fun onNetStatusReported(good: Boolean, uid: String) {
        if (good) {
            dismissPoorNetworkStatus()
        } else {
            showPoorNetworkStatus()
        }
    }

    override fun onAudioDeviceChanged(headset: Boolean) {
        if (headset) {
            headsetOn()
        } else {
            headsetOff()
        }
    }

    override fun onStatus(totalDurationInS: Long) {
        mCallDurationInS = totalDurationInS
    }

    override fun onMessageSend(message: CallMessage) {}

    override fun onMessageReceive(message: CallMessage) {
        when (message.getType()) {
            ACTION -> onActionMessageReceive(message)
            SYSTEM -> onSystemMessageReceive(message)
        }
    }

    override fun onRemoteVideoReceived(fuid: String) {
        CallManager.instance.setupRemoteVideo(mFragmentActivity.remote_video_container, fuid)
    }

    override fun onRemoteVideoDisableStateChanged(fuid: String, disable: Boolean) {
        super.onRemoteVideoDisableStateChanged(fuid, disable)
        mFragmentActivity.remote_video_off_tip.visibility = if (disable) View.VISIBLE else View.GONE
    }

    private fun onActionMessageReceive(message: CallMessage) {
        when (message.attrs?.optString("actionType")) {
            ActionMessageType.HANGUP -> {
                if (mIsCaller) {
                    sendCallMessage()
                }
                hangup(HangupReason.COIN_NOT_ENOUGH)
            }
        }
    }

    private fun onSystemMessageReceive(message: CallMessage) {
        when (message.attrs?.optString("subtype")) {
            SystemMessageType.EARNING_TIP -> {
                TextViewUtil.displayOrGone(mFragmentActivity.earning_tip, message.attrs?.optString("earningDesc"))
            }
            SystemMessageType.COIN_NOT_ENOUGH -> {
                val countDown = CountDown.createFromJson(message.attrs?.optJson("countDown"))
                mFragmentActivity.charge_layout.visibility = if (countDown.shouldDoUpdateByServers()) View.VISIBLE else View.GONE
                mCountDownDisposable?.dispose()
                mCountDownDisposable = Observable.interval(0, 1, TimeUnit.SECONDS)
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe {
                            if (!countDown.shouldDoUpdateByServers()) {
                                mCountDownDisposable?.dispose()
                                return@subscribe
                            }
                            mFragmentActivity.count_down_desc.text = mFragmentActivity.getString(R.string.call_coin_not_enough_count_down_desc, countDown.digitalCountDownTextByServers)
                        }
            }
        }
    }

    private fun checkNotificationPermission() {
        val notificationEnable: Boolean = CheckUtil.isNotificationChannelEnabled(mFragmentActivity, NotificationData.GroupId.CALL, NotificationData.ChannelId.CALL_CHAT)
        if (!notificationEnable) {
            showNotificationPermissionDialog()
        }
    }

    private fun showNotificationPermissionDialog() {
        AlertDialog.Builder(mFragmentActivity)
                .setMessage("请给予「电话」相关的通知权限，以保证 App 能够正常工作")
                .setPositiveButton("确定") { _, _ -> Router.instance.gotoNotificationSettings() }
                .show()
    }

    private fun loadBackground(avatar: SimpleDraweeView?, user: User) {
        avatar?.setViewTag(Constant.GlobalViewTagDefine.VIEW_TAG_FOR_USER, user)
        val thumbnailParam = UserImageLoadParam.newBuilder()
                .setUser(user, 56, 56) // same size with the avatar in R.layout.item_main_conversation
                .setPostProcessor(IterativeBoxBlurPostProcessor(2, 4))
                ?.setOverlayResId(R.drawable.chat_call_background_overlay_shape)
                ?.build()
        val param = UserImageLoadParam.newBuilder()
                .setUser(user, LocalDisplay.SCREEN_WIDTH_DP, LocalDisplay.SCREEN_HEIGHT_DP)
                .setPostProcessor(IterativeBoxBlurPostProcessor(2, 4))
                ?.setOverlayResId(R.drawable.chat_call_background_overlay_shape)
                ?.setLowResImageLoadParam(thumbnailParam)
                ?.build()
        ImageViewUtil.loadImage(avatar, param)
    }

    private fun logEventForCallFailed(responseType: String) {
        val param = Bundle()
        param.putString("disconnect_type", responseType)
        param.putInt("wait_time", if (mInitCallTimestamp == 0L) 0 else ((System.currentTimeMillis() - mInitCallTimestamp) / 1000).toInt())
        UTUtil.getInstance().logEvent("video_call_failed", param)
    }

    private fun updateCameraSwitch(selected: Boolean) {
        mFragmentActivity.voice_call_camera_switch.isSelected = selected
        mFragmentActivity.voice_receive_camera_switch.isSelected = selected
        mFragmentActivity.voice_receive_camera_switch.text = mFragmentActivity.getString(if (selected) R.string.video_call_camera_on else R.string.video_call_camera_off)
        mFragmentActivity.local_video_off_tip.visibility = if (selected) View.GONE else View.VISIBLE
    }

    private fun showHangupConfirmDialog() {
        AlertDialog.Builder(mFragmentActivity)
                .setMessage(R.string.video_call_hangup_confirm_dialog_message)
                .setPositiveButton(R.string.action_confirm) { dialog, which ->
                    sendCallMessage()
                    hangup(HangupReason.HANGUP)
                }
                .setNegativeButton(R.string.action_cancel) { _, _ -> }
                .show()
    }

    fun enqueueOrPlayGiftAnimation(gift: Gift) {
        if (isGiftAnimationPlaying) {
            mAnimationQueue.offer(gift)
        } else {
            playGiftAnimation(gift)
        }
    }

    private fun playGiftAnimation(gift: Gift) {
        isGiftAnimationPlaying = true
        // If I'm an receiver, notice server that this gift has been played
        if (UserData.getInstance().isSelf(gift.toUid)) {
            GiftData.playGiftAnimation(gift.billId)
        }

        if (gift.hasSvga()) {
            RxUtils.disposable(mSvgaAnimationDisposable)
            mSvgaAnimationDisposable = SvgaData.createTaskFromAsset(gift.getSvgaFilename())
                    .compose(RxUtils.ioToMainSingleScheduler())
                    .subscribe({ entity ->
                        mFragmentActivity.svga_animation_view.visibility = View.VISIBLE
                        mFragmentActivity.svga_animation_view.setImageDrawable(SVGADrawable(entity))
                        mFragmentActivity.svga_animation_view.startAnimation()
                    }, { t -> t.printStackTrace() })
        } else {
            mGiftAnimator = ViewAnimator.animate(mFragmentActivity.animation_view)
                    .alpha(0F, 1F)
                    .scale(0F, 1F)
                    .dp().translationY(0F, 200F)
                    .interpolator(AccelerateDecelerateInterpolator())
                    .duration(1500)
                    .onStart {
                        ImageViewUtil.loadImage(mFragmentActivity.animation_view, ImageLoadParam.newBuilder().setUrl(gift.icon).setSizeInDP(72).build())
                        mFragmentActivity.animation_view.visibility = View.VISIBLE
                    }
                    .thenAnimate(mFragmentActivity.animation_view)
                    .alpha(1F, 0F)
                    .scale(1F, 0F)
                    .dp().translationY(200F, 540F)
                    .interpolator(DecelerateInterpolator())
                    .duration(1800)
                    .onStop {
                        mFragmentActivity.animation_view.visibility = View.GONE
                        isGiftAnimationPlaying = false

                        if (mAnimationQueue.isNotEmpty()) {
                            playGiftAnimation(mAnimationQueue.poll())
                        }
                    }
                    .start()
        }
    }

    private fun clearGiftAnimation() {
        mAnimationQueue.clear()
        mFragmentActivity.svga_animation_view.visibility = View.GONE
        mFragmentActivity.svga_animation_view.stopAnimation()
        mSvgaAnimationDisposable?.dispose()
        mGiftAnimator?.cancel()
        isGiftAnimationPlaying = false
        mFragmentActivity.animation_view.visibility = View.GONE
    }

    override fun onLocateVideoDisableStateChanged(disable: Boolean) {
        super.onLocateVideoDisableStateChanged(disable)
        logEventForVideoDisable(disable)
    }

    override fun onLocalVideoFailure() {
        MixDataUploader.instance.submit(UUID.randomUUID().toString())
    }

    private fun logEventForVideoDisable(disable: Boolean) {
        if (disable) {
            mDisableVideoStartTime = System.currentTimeMillis()
            VideoCallLogger.disableVideo()
        } else {
            val duration = (System.currentTimeMillis() - mDisableVideoStartTime) / 1000
            VideoCallLogger.enableVideo(duration)
            mDisableVideoStartTime = 0
        }
    }
}